﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MetalSoda
{
    /// <summary>
    /// SSB message type
    /// </summary>
    [DebuggerDisplay("MessageType: {Name}, Contracts: {ContractsCount}, Services: {ServicesCount}, Queues: {QueuesCount}")]
    public class MessageType :IDdlObject
    {
        /// <summary>
        /// Message type name
        /// </summary>
        public readonly string Name;

        #region Private Fields
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly OneToManyAssociation<MessageType, Contract> m_contracts;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly Dictionary<Contract, SentBy> m_contractUsage = new Dictionary<Contract, SentBy>();

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Validation m_validation;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string m_ownerName; 
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MetalSoda.MessageType"/> class.
        /// </summary>
        /// <param name="name">Message type name</param>
        public MessageType(string name)
        {
            Name = name;
            m_validation = Validation.None;
            m_contracts = new OneToManyAssociation<MessageType, Contract>(this,
                added: args => args.Ref.AddMessageType(this, SendBy(args.Ref)),
                removed: args => { args.Ref.RemoveMessageType(this); });
        }

        #region Properties
        /// <summary>
        /// Gets or sets the owner of the message type to the specified database user or role. 
        /// </summary>
        /// <remarks>
        /// When the current user is dbo or sa, owner_name can be the name of any valid user or role. 
        /// Otherwise, owner_name must be the name of the current user, the name of a user who the current user has IMPERSONATE permission for, or the name of a role to which the current user belongs. 
        /// </remarks>
        public string OwnerName
        {
            get { return (m_ownerName ?? (m_ownerName = "dbo")); }
            set { m_ownerName = string.IsNullOrWhiteSpace(value) ? "dbo" : value.Trim(); }
        }

        /// <summary>
        /// Gets the rule determining how Service Broker validates the message body for messages of this type (defaults is NONE).
        /// </summary>
        public Validation Validation
        {
            get { return m_validation ?? (m_validation = Validation.None); }
            set { m_validation = (value ?? Validation.None); }
        }

        /// <summary>
        /// Gets total number of contracts that support this message type.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int ContractsCount { get { return m_contracts.Count; } }

        /// <summary>
        /// Gets total number of services that support this message type.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int ServicesCount { get { return Services.Count(); } }

        /// <summary>
        /// Gets total number of queues that can receive messages of this type.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int QueuesCount { get { return Queues.Count(); } }

        /// <summary>
        /// Gets contracts supporting this message type
        /// </summary>
        public IEnumerable<Contract> Contracts { get { return m_contracts.AsEnumerable(); } }

        /// <summary>
        /// Gets services supporting this message type
        /// </summary>
        public IEnumerable<Service> Services { get { return m_contracts.SelectMany(c => c.Services).Distinct(); } }

        /// <summary>
        /// Gets queues that can receive messages of this message type.
        /// </summary>
        public IEnumerable<Queue> Queues { get { return Services.Select(s => s.Queue).Where(q => q != null).Distinct(); } } 
        #endregion

        #region Methods
        /// <summary>
        /// Registers the specified contract to support thismessage type.
        /// </summary>
        /// <param name="contract">Contract to support this message type.</param>
        /// <param name="sendBy">Type of the service endpoint that can send messages of this type.</param>
        /// <returns>Self</returns>
        public MessageType AddContract(Contract contract, SentBy sendBy)
        {
            if (m_contracts.Contains(contract))
            {
                if (m_contractUsage[contract] == sendBy) { return this; }
                var msg = string.Format("The {0} contract already exists.", contract.Name);
                throw new InvalidOperationException(msg);
            }
            m_contractUsage[contract] = sendBy;
            m_contracts.Add(contract);
            return this;
        }

        /// <summary>
        /// Removes the specified contract from contracts supporting this message type.
        /// </summary>
        /// <param name="contract">Contract to not support this message type.</param>
        /// <returns>Self</returns>
        public MessageType RemoveContract(Contract contract)
        {
            m_contractUsage.Remove(contract);
            m_contracts.Remove(contract);
            return this;
        }

        /// <summary>
        /// Finds by name the contract supporting this message type.
        /// </summary>
        /// <param name="contractName">Contract name.</param>
        /// <returns>Contract</returns>
        public Contract Contract(string contractName)
        {
            return m_contracts.Where(c => Utils.AreSame(c.Name, contractName)).SingleOrDefault();
        }

        /// <summary>
        /// Finds type of the service endpoint that can send messages of this message type on the specified contract.
        /// </summary>
        /// <param name="contract">Contract instance</param>
        /// <returns>Type of the service endpoint.</returns>
        public SentBy SendBy(Contract contract)
        {
            SentBy sendBy = MetalSoda.SentBy.Any;
            m_contractUsage.TryGetValue(contract, out sendBy);
            return sendBy;
        }

        /// <summary>
        /// Finds type of the service endpoint that can send messages of this message type on the specified contracts name.
        /// </summary>
        /// <param name="contractName">Contract name</param>
        /// <returns>Type of the service endpoint.</returns>
        public SentBy SendBy(string contractName)
        {
            return SendBy(Contract(contractName));
        } 
        #endregion

        #region IDdlObject Implementation
        /// <summary>
        /// Generates a sequence on the CREATE batches for this message type.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateCreateBatches()
        {
            yield return new SQLScript.CreateMessageTypeRuntimeTextTemplate(this).TransformText();
        }

        /// <summary>
        /// Generates a sequence on the ALTER batches for this message type.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateAlterBatches()
        {
            yield return new SQLScript.AlterMessageTypeRuntimeTextTemplate(this).TransformText();
        }

        /// <summary>
        /// Generates a sequence on the DELETE batches for this message type.
        /// </summary>
        /// <returns>T-SQL batches</returns>
        public IEnumerable<string> GenerateDeleteBatches() { yield return string.Format("DROP MESSAGE TYPE {0};", this.Name); } 
        #endregion
    }
}
